package com.money.tracker.auth.service.impl;

import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.money.tracker.common.constant.MoneyTrackerConstant;
import com.money.tracker.common.entity.LoginUser;
import com.money.tracker.common.enums.DeviceType;
import com.money.tracker.common.enums.IResponseEnum;
import com.money.tracker.common.enums.RedisKeyEnum;
import com.money.tracker.common.enums.UserTypeEnum;
import com.money.tracker.common.exception.MoneyTrackerBizException;
import com.money.tracker.common.utils.LoginUtil;
import com.money.tracker.common.helper.QueryCacheHelper;
import com.money.tracker.common.utils.GeneratorCodeUtil;
import com.money.tracker.common.utils.PasswordValidatorUtil;
import com.money.tracker.common.helper.RedisHelper;
import com.money.tracker.auth.entity.SysDept;
import com.money.tracker.auth.entity.SysUser;
import com.money.tracker.auth.entity.req.LoginReq;
import com.money.tracker.auth.entity.rsp.CaptChaRsp;
import com.money.tracker.auth.entity.rsp.LoginRsp;
import com.money.tracker.auth.entity.rsp.UserInfoRsp;
import com.money.tracker.auth.enums.CaptchaCategoryEnum;
import com.money.tracker.auth.enums.CaptchaTypeEnum;
import com.money.tracker.auth.enums.UserStatusEnum;
import com.money.tracker.auth.service.ISysDeptService;
import com.money.tracker.auth.service.SysLoginService;
import com.money.tracker.auth.service.SysPermissionService;
import com.money.tracker.auth.service.SysUserService;
import com.money.tracker.auth.service.transfer.SysTransferService;
import com.money.tracker.system.config.CaptchaConfig;
import com.money.tracker.system.entity.SysConfig;
import com.money.tracker.system.service.SysConfigService;
import com.money.tracker.system.service.SysLogService;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;

/**
 * <p>
 * 登录接口实现
 * <p>
 *
 * @author zhangshuai
 * &#064;@date 2024/09/25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements SysLoginService {

    private final SysConfigService sysConfigService;
    private final SysUserService sysUserService;
    private final SysTransferService sysTransferService;
    private final RedisHelper redisHelper;
    private final SysPermissionService sysPermissionService;
    private final ISysDeptService sysDeptService;
    private final QueryCacheHelper queryCacheHelper;
    private final CaptchaConfig captchaConfig;
    private final SysLogService sysLogService;
    private final JavaMailSender mailSender;

    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;

    @Value("${user.password.lockTime}")
    private Integer lockTime;

    @Value("${spring.mail.username}")
    private String from;
    /**
     * 登录
     *
     * @param loginReq
     * @return {@link LoginRsp }
     */

    @Override
    public LoginRsp login(LoginReq loginReq) {
        // 判断验证码是否开启
        if (verifyCode()) {
            validateCaptcha(loginReq.getUsername(), loginReq.getCode(), loginReq.getUuid());
        }
        // 拿当前登录用户
        LambdaQueryWrapper<SysUser> userLambdaQueryWrapper = new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUserName, loginReq.getUsername());
        SysUser sysUser = sysUserService.getOne(userLambdaQueryWrapper);
        // 用户不存在
        if (Objects.isNull(sysUser)) {
            throw new MoneyTrackerBizException(IResponseEnum.USER_NOT_EXIST);
        }
        // 用户被禁用
        if (Objects.equals(UserStatusEnum.DISABLE.getCode(), sysUser.getStatus())) {
            throw new MoneyTrackerBizException(IResponseEnum.USER_DISABLE);
        }
        // 用户校验 --- 错误规定的次数之后锁定账户10分钟
        checkSysUser(loginReq, sysUser.getPassword());
        LoginUser loginUser = buildLoginUser(sysUser);
        // 生成令牌
        LoginUtil.loginByDevice(loginUser, DeviceType.PC);
        return LoginRsp.builder()
                .token(StpUtil.getTokenValue())
                .build();
    }

    /**
     * 校验验证码正确性
     *
     * @param username
     * @param code
     * @param uuid
     */

    private void validateCaptcha(String username, String code, String uuid) {
        String verifyKey = RedisKeyEnum.getKey(RedisKeyEnum.CAPTCHA_CODE_KEY, uuid);
        String captcha = redisHelper.getCacheObject(verifyKey);
        redisHelper.deleteObject(verifyKey);
        if (captcha == null) {
            sysLogService.recordLoginLog(sysTransferService.buildSysLog(username, IResponseEnum.CAPTCHA_INVALID));
            throw new MoneyTrackerBizException(IResponseEnum.CAPTCHA_ERROR);
        }

        if (!StringUtils.equalsIgnoreCase(captcha, code)) {
            sysLogService.recordLoginLog(sysTransferService.buildSysLog(username, IResponseEnum.CAPTCHA_ERROR));
            throw new MoneyTrackerBizException(IResponseEnum.CAPTCHA_ERROR);
        }
    }

    /**
     * 判断是否开启验证码开关
     *
     * @return boolean
     */

    private boolean verifyCode() {
        // 判断是否开启验证码开关
        SysConfig sysConfig = sysConfigService.getSysConfig(MoneyTrackerConstant.CAPTCHA_CODE_KEY);
        return Convert.toBool(sysConfig.getConfigValue());

    }

    private LoginUser buildLoginUser(SysUser sysUser) {
        // 菜单权限
        Set<String> menuPermission = sysPermissionService.getMenuPermission(sysUser);
        // 角色权限
        Set<String> rolePermission = sysPermissionService.getRolePermission(sysUser);
        // 部门
//        String key = RedisKeyEnum.getKey(RedisKeyEnum.KEY_DEPT, sysUser.getDeptId());
//        SysDept sysDept = queryCacheUtil.queryWithCache(key, RedisKeyEnum.KEY_DEPT.getExpire(), RedisKeyEnum.KEY_DEPT.getTimeUnit(),
//                    () -> sysDeptService.getOne(new LambdaQueryWrapper<SysDept>()
//                        .eq(SysDept::getId, sysUser.getDeptId())));
        SysDept sysDept = new SysDept();
        return sysTransferService.buildLoginUser(sysUser, menuPermission, rolePermission, sysDept);
    }

    @Override
    public void logout() {
        StpUtil.logout();
    }


    /**
     * 获取验证码
     *
     * @return {@link CaptChaRsp }
     */

    @Override
    public CaptChaRsp getCode() {
        if (!verifyCode()) {
            return CaptChaRsp.builder().captchaEnabled(false).build();
        }
        // 生成验证码
        CaptchaTypeEnum captchaType = captchaConfig.getType();
        boolean isMath = CaptchaTypeEnum.MATH == captchaType;
        Integer length = isMath ? captchaConfig.getNumberLength() : captchaConfig.getCharLength();
        CodeGenerator codeGenerator = CaptchaTypeEnum.getCodeGenerator(captchaType, length);
        AbstractCaptcha captcha = CaptchaCategoryEnum.getAbstractCaptcha(captchaConfig.getCategory());
        captcha.setGenerator(codeGenerator);
        captcha.createCode();
        String code = captcha.getCode();
        if (isMath) {
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
            code = exp.getValue(String.class);
        }
        String uuid = IdUtil.simpleUUID();
        String verifyKey = RedisKeyEnum.getKey(RedisKeyEnum.CAPTCHA_CODE_KEY, uuid);
        redisHelper.setCacheObject(verifyKey, code, RedisKeyEnum.CAPTCHA_CODE_KEY.getExpire(), RedisKeyEnum.CAPTCHA_CODE_KEY.getTimeUnit());
        return CaptChaRsp.builder()
                .img(captcha.getImageBase64())
                .uuid(uuid)
                .captchaEnabled(true)
                .build();
    }

    @Override
    public void sendEmail(LoginReq loginReq) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message);
            helper.setFrom(from);
            helper.setTo(loginReq.getEmail());
            helper.setSubject("lowcode账号注册");
            String code = GeneratorCodeUtil.getRandomCode(6);
            String key = RedisKeyEnum.getKey(RedisKeyEnum.CAPTCHA, code);
            redisHelper.setCacheObject(key, "1", RedisKeyEnum.CAPTCHA.getExpire(), RedisKeyEnum.CAPTCHA.getTimeUnit());
            helper.setText("您当前的验证码为：" + code + "，3分钟内有效。感谢您成为lowcode一员。");
            mailSender.send(helper.getMimeMessage());
        } catch (Exception e) {
            log.error("发送邮件失败:{}" ,e.getMessage());
            throw new MoneyTrackerBizException("发送邮件失败");
        }
    }

    @Override
    public LoginRsp register(LoginReq req) {
        // 校验当前key是否存在
        String key = RedisKeyEnum.getKey(RedisKeyEnum.CAPTCHA, req.getCode());
        String code = redisHelper.getCacheObject(key);
        if (StringUtils.isBlank(code)) {
            throw new MoneyTrackerBizException("请输入正确的邮箱验证码");
        }
        // 校验密码是否合规
        boolean isValidate = PasswordValidatorUtil.validatePassword(req.getPassword());
        if (!isValidate){
            throw new MoneyTrackerBizException("密码格式有误");
        }
        // 校验用户是否已存在
        if (sysUserService.count(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUserName, req.getUsername())) > 0) {
            throw new MoneyTrackerBizException("用户名已存在");
        }
        // 创建用户
        SysUser sysUser = buildUser(req);
        sysUserService.save(sysUser);
        LoginUser loginUser = sysTransferService.buildLoginUser(sysUser, Collections.emptySet(), Collections.emptySet(), new SysDept());
        // 生成token
        LoginUtil.loginByDevice(loginUser, DeviceType.PC);
        // 清除验证码
        redisHelper.deleteObject(key);
        return LoginRsp.builder()
                .token(StpUtil.getTokenValue())
                .build();
    }

    @Override
    public UserInfoRsp getUserDetail() {
        LoginUser loginUser = LoginUtil.getLoginUser();
        if (Objects.isNull(loginUser)){
            throw new MoneyTrackerBizException("当前用户未登录，请登录");
        }
        SysUser sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getId, loginUser.getUserId()));
        return sysTransferService.buildUserInfo(sysUser, loginUser);
    }

    private SysUser buildUser(LoginReq req) {
        return SysUser.builder()
                .userName(req.getUsername())
                .password(BCrypt.hashpw(req.getPassword()))
                .loginDate(LocalDateTime.now())
                .email(req.getEmail())
                .userType(UserTypeEnum.SYS_USER.getUserType())
                .build();
    }


    /**
     * 校验用户登录错误次数
     *
     * @param loginReq
     * @param password
     */

    private void checkSysUser(LoginReq loginReq, String password) {
        String key = RedisKeyEnum.getKey(RedisKeyEnum.PWD_ERR_CNT_KEY, loginReq.getUsername());
        Integer errCnt = ObjectUtil.defaultIfNull(redisHelper.getCacheObject(key), 0);
        // 错误次数过多
        if (errCnt >= maxRetryCount) {
            // TODO: 记录登录错误日志
            throw new MoneyTrackerBizException(IResponseEnum.PWD_ERR_CNT);
        }
        // 判断账户密码是否正确
        if (!BCrypt.checkpw(loginReq.getPassword(), password)) {
            errCnt++;
            redisHelper.setCacheObject(key, errCnt, lockTime, RedisKeyEnum.PWD_ERR_CNT_KEY.getTimeUnit());
            throw new MoneyTrackerBizException(IResponseEnum.PWD_ERR);
        }
        // 登录成功，清空除错误次数
        redisHelper.deleteObject(key);
    }
}
